/** 
 * Copyright (c) 2008 Todd A. Fisher
 * see LICENSE
 */
#include "ruby.h"
#include "parser.h"
#define DEBUG
#ifdef DEBUG
#define TRACE()  fprintf(stderr, "> %s:%d:%s\n", __FILE__, __LINE__, __FUNCTION__)
#else
#define TRACE() 
#endif

/* ruby 1.9 compat */
#ifndef RSTRING_PTR
#define RSTRING_PTR(str) RSTRING(str)->ptr
#endif

#ifndef RSTRING_LEN
#define RSTRING_LEN(str) RSTRING(str)->len
#endif

#define OUTPUT_BUFFER_SIZE 1024

static VALUE rb_ESI;
static VALUE rb_ESIParser;

// TODO: we can use rb_iv_set instead and just pass the whole parser as the user_data to each call back
typedef struct _ParserUserData{
  VALUE output_block;
  VALUE start_tag_block;
  VALUE end_tag_block;
  VALUE depth;
  VALUE esi_tag;
  char buffer[OUTPUT_BUFFER_SIZE]; /* buffer bytes before sending them to ruby */
  size_t buffered; /* amount buffered */
}ParserUserData;

static 
VALUE ESIParser_set_output_cb( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  if( !rb_block_given_p() ){
    rb_raise(rb_eArgError, "Block expected. Call this method with a closure. { ... }");
  }

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  user_data->output_block = rb_block_proc();

  return Qnil;
}

static
VALUE ESIParser_set_start_tag_cb( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  if( !rb_block_given_p() ){
    rb_raise(rb_eArgError, "Block expected. Call this method with a closure. { ... }");
  }

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  user_data->start_tag_block = rb_block_proc();

  return Qnil;
}

static
VALUE ESIParser_set_end_tag_cb( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  if( !rb_block_given_p() ){
    rb_raise(rb_eArgError, "Block expected. Call this method with a closure. { ... }");
  }

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  user_data->end_tag_block = rb_block_proc();

  return Qnil;
}

static 
void ESIParser_free( ESIParser *parser )
{
  ParserUserData *user_data = (ParserUserData*)parser->user_data;

  free( user_data );
  parser->user_data = NULL;

  esi_parser_free( parser );
}
static
void ESIParser_mark( ESIParser *parser )
{
  ParserUserData *user_data = (ParserUserData*)parser->user_data;
  if( user_data ){

    if( !NIL_P( user_data->output_block ) )
      rb_gc_mark( user_data->output_block );
    if( !NIL_P( user_data->start_tag_block ) )
      rb_gc_mark( user_data->start_tag_block );
    if( !NIL_P( user_data->end_tag_block ) )
      rb_gc_mark( user_data->end_tag_block );
    if( !NIL_P( user_data->depth ) )
      rb_gc_mark( user_data->depth );
    if( !NIL_P( user_data->esi_tag ) )
      rb_gc_mark( user_data->esi_tag );

  }
}

/* default callbacks */
static void 
esi_parser_default_start_cb( const void *data,
                             const char *name_start,
                             size_t name_length,
                             ESIAttribute *attr,
                             void *user_data )
{
  ParserUserData *ud = (ParserUserData*)user_data;
  if( ud->start_tag_block && !NIL_P(ud->start_tag_block) && rb_respond_to( ud->start_tag_block, rb_intern("call") ) ){
    VALUE attr_hash = rb_hash_new();
    VALUE tag_text = rb_str_new( name_start, name_length );
    while( attr ){
      VALUE name = rb_str_new( attr->name, strlen( attr->name ) );
      VALUE value = rb_str_new( attr->value, strlen( attr->value ) );
      rb_hash_aset( attr_hash, name, value );
      attr = attr->next;
    }
    rb_funcall( ud->start_tag_block, rb_intern("call"), 2, tag_text, attr_hash );
  }
}

static void 
esi_parser_default_end_cb( const void *data,
                           const char *name_start,
                           size_t name_length,
                           void *user_data )
{
  ParserUserData *ud = (ParserUserData*)user_data;
  if( ud->end_tag_block && !NIL_P(ud->end_tag_block) && rb_respond_to( ud->end_tag_block, rb_intern("call") ) ){
    VALUE tag_text =  rb_str_new( name_start, name_length );
    rb_funcall( ud->end_tag_block, rb_intern("call"), 1, tag_text );
  }
}

static void
send_output( VALUE output, VALUE esi_tag_context, VALUE rbstr )
{
  if( !NIL_P( esi_tag_context ) && rb_respond_to( esi_tag_context, rb_intern("buffer") ) ){
    /* a tag is currently open so send the output to it so that i can decide if the output is ready or not */
    rb_funcall( esi_tag_context, rb_intern("buffer"), 2, output, rbstr );
  }
  else{
    /* if no tag is in the current context send the output directly to the device */
    if( rb_respond_to( output, rb_intern("call") ) ){
      rb_funcall( output, rb_intern("call"), 1, rbstr );
    }
    else{
      rb_funcall( output, rb_intern("<<"), 1, rbstr );
    }
  }
}

static void 
esi_parser_default_output_cp( const void *data,
                              size_t length,
                              void *user_data )
{
  VALUE rbstr;
  ParserUserData *ud = (ParserUserData*)user_data;

  if( ud->output_block && !NIL_P(ud->output_block) ){
    rbstr = rb_str_new( data, length );
    send_output( ud->output_block, ud->esi_tag, rbstr );
    /* buffer the output to the ruby script */
    /*if( ud->buffered+length < OUTPUT_BUFFER_SIZE ){
      memcpy( ud->buffer + ud->buffered, data, length );
      ud->buffered += length;
    }
    else{
      if( ud->buffered > 0 ){
        rbstr = rb_str_new( ud->buffer, ud->buffered );
        send_output( ud->output_block, ud->esi_tag, rbstr );
        ud->buffered = 0;
      }
      rbstr = rb_str_new( data, length );
      send_output( ud->output_block, ud->esi_tag, rbstr );
    }*/
    //rb_funcall( ud->output_block, rb_intern("call"), 1, rb_str_new( data, length ) );
  }
}

static
VALUE ESIParser_process( VALUE self, VALUE data )
{
  ESIParser *parser;

  Data_Get_Struct( self, ESIParser, parser );

  esi_parser_execute( parser, RSTRING_PTR( data ), RSTRING_LEN( data ) );

  return Qnil;
}

static
VALUE ESIParser_finish( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );
  user_data = (ParserUserData*)parser->user_data;

  esi_parser_finish( parser );
/*
  if( user_data->buffered > 0 ){
    VALUE rbstr = rb_str_new( user_data->buffer, user_data->buffered );
    send_output( user_data->output_block, user_data->esi_tag, rbstr );
    memset( user_data->buffer, 0, OUTPUT_BUFFER_SIZE );
    user_data->buffered = 0;
  }
  */

  return Qnil;
}

static
VALUE ESIParser_alloc(VALUE klass)
{
  VALUE object;
  ESIParser *parser = esi_parser_new();

  ParserUserData *user_data = (ParserUserData*)malloc(sizeof(ParserUserData));

  user_data->output_block = Qnil;
  user_data->start_tag_block = Qnil;
  user_data->end_tag_block = Qnil;
  user_data->depth = rb_int_new(0);
  user_data->esi_tag = Qnil;
  memset( user_data->buffer, 0, OUTPUT_BUFFER_SIZE );
  user_data->buffered = 0;

  parser->user_data = user_data;

  esi_parser_init( parser );

  esi_parser_start_tag_handler( parser, esi_parser_default_start_cb );

  esi_parser_end_tag_handler( parser, esi_parser_default_end_cb );

  esi_parser_output_handler( parser, esi_parser_default_output_cp );

  object = Data_Wrap_Struct( klass, ESIParser_mark, ESIParser_free, parser );

  return object;
}

static
VALUE ESIParser_set_esi_tag( VALUE self, VALUE esi_tag )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  user_data->esi_tag = esi_tag;

  return esi_tag;
}

static
VALUE ESIParser_get_esi_tag( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  return user_data->esi_tag;
}

static
VALUE ESIParser_set_depth( VALUE self, VALUE depth )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  user_data->depth = depth;

  return depth;
}

static
VALUE ESIParser_get_depth( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  return user_data->depth;
}

static
VALUE ESIParser_set_output( VALUE self, VALUE output )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  user_data->output_block = output;

  return output;
}

static
VALUE ESIParser_get_output( VALUE self )
{
  ESIParser *parser;
  ParserUserData *user_data;

  Data_Get_Struct( self, ESIParser, parser );

  user_data = (ParserUserData*)parser->user_data;

  return user_data->output_block;
}
static
VALUE ESIParser_send_output( VALUE self, VALUE output )
{
  ESIParser *parser;

  Data_Get_Struct( self, ESIParser, parser );

  esi_parser_default_output_cp( RSTRING_PTR(output), RSTRING_LEN(output), parser->user_data );

  return Qnil;
}
void Init_esi()
{
  rb_ESI = rb_define_module( "ESI" );
  rb_ESIParser = rb_define_class_under( rb_ESI, "CParser", rb_cObject );

  rb_define_alloc_func( rb_ESIParser, ESIParser_alloc );

  rb_define_method( rb_ESIParser, "output_handler", ESIParser_set_output_cb, 0 );
  rb_define_method( rb_ESIParser, "start_tag_handler", ESIParser_set_start_tag_cb, 0 );
  rb_define_method( rb_ESIParser, "end_tag_handler", ESIParser_set_end_tag_cb, 0 );
  rb_define_method( rb_ESIParser, "process", ESIParser_process, 1 );
  rb_define_method( rb_ESIParser, "finish", ESIParser_finish, 0 );
  rb_define_method( rb_ESIParser, "esi_tag=", ESIParser_set_esi_tag, 1 );
  rb_define_method( rb_ESIParser, "esi_tag", ESIParser_get_esi_tag, 0 );
  rb_define_method( rb_ESIParser, "depth=", ESIParser_set_depth, 1 );
  rb_define_method( rb_ESIParser, "depth", ESIParser_get_depth, 0 );
  rb_define_method( rb_ESIParser, "output", ESIParser_get_output, 0 );
  rb_define_method( rb_ESIParser, "output=", ESIParser_set_output, 1 );
  rb_define_method( rb_ESIParser, "output<<", ESIParser_send_output, 1 );

}
